package jdk;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;

public class ServiceLoader<S> implements Iterable<S> {

	@Override
	public Iterator<S> iterator() {
		return providers.iterator();
	}

	// 对应的接口 Class 模板
	private final Class<S> service;

	// 对应实现类的 可以有多个，用 List 进行封装
	private final List<S> providers = new ArrayList<>();

	// 类加载器
	private final ClassLoader classLoader;

	// 暴露给外部使用的方法，通过调用这个方法可以开始加载自己定制的实现流程。
	public static <S> ServiceLoader<S> load(Class<S> service) {
		return new ServiceLoader<>(service);
	}

	// 构造方法私有化
	private ServiceLoader(Class<S> service) {
		this.service = service;
		this.classLoader = Thread.currentThread().getContextClassLoader();
		doLoad();
	}

	// 关键方法，加载具体实现类的逻辑
	private void doLoad() {
		try {
			// 读取所有 jar 包里面 META-INF/services 包下面的文件，这个文件名就是接口名，然后文件里面的内容就是具体的实现类的路径加全类名
			Enumeration<URL> urls = classLoader.getResources("META-INF/services/" + service.getName());
			// 挨个遍历取到的文件
			while (urls.hasMoreElements()) {
				// 取出当前的文件
				URL url = urls.nextElement();
				// System.out.println("File = " + url.getPath());
				// 建立链接
				URLConnection urlConnection = url.openConnection();
				urlConnection.setUseCaches(false);
				// 获取文件输入流
				InputStream inputStream = urlConnection.getInputStream();
				// 从文件输入流获取缓存
				BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
				// 从文件内容里面得到实现类的全类名
				String className = bufferedReader.readLine();

				while (className != null) {
					// 通过反射拿到实现类的实例
					Class<?> clazz = Class.forName(className, false, classLoader);
					// 如果声明的接口跟这个具体的实现类是属于同一类型，（可以理解为Java的一种多态，接口跟实现类、父类和子类等等这种关系。）则构造实例
					if (service.isAssignableFrom(clazz)) {
						Constructor<? extends S> constructor = (Constructor<? extends S>) clazz.getConstructor();
						S instance = constructor.newInstance();
						// 把当前构造的实例对象添加到 Provider的列表里面
						providers.add(instance);
					}
					// 继续读取下一行的实现类，可以有多个实现类，只需要换行就可以了。
					className = bufferedReader.readLine();
				}
			}
		} catch (Exception e) {
			System.out.println("读取文件异常。。。");
		}
	}

	// 返回spi接口对应的具体实现类列表
	public List<S> getProviders() {
		return providers;
	}
}
